/*
 * @Author: 张宇飞
 * @Date: 2020-02-29 17:25:17
 * @LastEditors: 张宇飞
 * @LastEditTime: 2020-03-02 17:01:45
 * @Description: 信号量的使用示例。这里只是演示线程间使用。
 * @Email: zhangyufei49@163.com
 */

#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>

#include "utils.h"

#define print_test_header() \
    fprintf(stderr, "\n------------ %s ------------\n\n", __func__)

// 使用信号量来模拟一个简单的互斥锁
typedef sem_t mutex_t;
#define mutex_init(mutex) sem_init((mutex), 0, 1)
#define mutex_destroy sem_destroy
#define mutex_lock(mutex) sem_wait((mutex))
#define mutex_unlock(mutex) sem_post((mutex))

static void* mutex_test_func(void* arg) {
    show_thread_msg("enter");
    mutex_t* mutex = (mutex_t*)arg;
    show_thread_msg("require lock");
    mutex_lock(mutex);
    show_thread_msg("locked and sleep 3 secs ............");
    sleep(3);
    show_thread_msg("wakeup");
    mutex_unlock(mutex);
    show_thread_msg("unlocked");
    show_thread_msg("exit");
    return NULL;
}

static void test_mutex() {
    print_test_header();
    mutex_t mutex;
    mutex_init(&mutex);
    run_threads_with_args(mutex_test_func, &mutex, mutex_test_func, &mutex,
                          NULL);
    mutex_destroy(&mutex);
}

// 读写锁
typedef struct {
    sem_t rsem;
    sem_t wsem;
    int readers;
} rwlock_t;

static int rwlock_init(rwlock_t* lock) {
    lock->readers = 0;
    return sem_init(&lock->rsem, 0, 1) || sem_init(&lock->wsem, 0, 1);
}

static int rwlock_destroy(rwlock_t* lock) {
    return sem_destroy(&lock->rsem) || sem_destroy(&lock->wsem);
}

static int rwlock_rdlock(rwlock_t* lock) {
    sem_wait(&lock->rsem);
    if (lock->readers == 0) {
        sem_wait(&lock->wsem);
    }
    lock->readers++;
    return sem_post(&lock->rsem);
}

static int rwlock_rdunlock(rwlock_t* lock) {
    sem_wait(&lock->rsem);
    lock->readers--;
    if (lock->readers == 0) {
        sem_post(&lock->wsem);
    }
    return sem_post(&lock->rsem);
}

inline static int rwlock_wrlock(rwlock_t* lock) {
    return sem_wait(&lock->wsem);
}

inline static int rwlock_wrunlock(rwlock_t* lock) {
    return sem_post(&lock->wsem);
}

typedef struct {
    rwlock_t* lock;
    unsigned int secs;
} rwlock_test_conf_t;

static void* rwlock_read_func(void* arg) {
    pthread_t pid = pthread_self();
    rwlock_test_conf_t* test = (rwlock_test_conf_t*)arg;

    // 休眠
    sleep(test->secs);
    show_thread_msg("start require read lock");
    // 申请读锁
    rwlock_rdlock(test->lock);
    show_thread_msg("has got read lock");
    sleep(5);
    rwlock_rdunlock(test->lock);
    show_thread_msg("release read lock");
    return NULL;
}

static void* rwlock_write_func(void* arg) {
    pthread_t pid = pthread_self();
    rwlock_test_conf_t* test = (rwlock_test_conf_t*)arg;

    // 休眠
    sleep(test->secs);
    show_thread_msg("start require write lock");
    // 申请写锁
    rwlock_wrlock(test->lock);
    show_thread_msg("has got write lock");
    sleep(5);
    rwlock_wrunlock(test->lock);
    show_thread_msg("release write lock");
    return NULL;
}

static void test_rwlock() {
    print_test_header();
    rwlock_t lock;
    rwlock_init(&lock);
    // 用于上读锁测试
    rwlock_test_conf_t test_confs[3] = {
        {&lock, 0},
        {&lock, 1},
        {&lock, 2},
    };

    fprintf(stderr, "先上读锁的测试\n");
    run_threads_with_args(rwlock_read_func, &test_confs[0], rwlock_read_func,
                          &test_confs[1], rwlock_write_func, &test_confs[2],
                          NULL);
    fprintf(stderr, "\n先上写锁的测试\n");
    run_threads_with_args(rwlock_write_func, &test_confs[0], rwlock_read_func,
                          &test_confs[1], rwlock_read_func, &test_confs[2],
                          NULL);

    rwlock_destroy(&lock);
}

// 生产消费者测试
static sem_t psem, csem;
#define CARGO_SIZE 3
static int cargo[CARGO_SIZE] = {0};

static void* producer(void* arg) {
    fprintf(stderr, "producer start\n");
    int total = 6;
    int i = 0;
    while (total-- > 0) {
        sem_wait(&psem);
        cargo[i] = rand() % 1000 + 1;
        fprintf(stderr, "\tproduce %d\n", cargo[i]);
        // 通知消费者可以消费了
        sem_post(&csem);
        i = (i + 1) % CARGO_SIZE;
        sleep(rand() % 2 + 1);
    }
    fprintf(stderr, "producer exit\n");
    return NULL;
}

static void* consumer(void* arg) {
    fprintf(stderr, "consumer start\n");
    int i = 0;
    struct timespec tm = {0, 0};
    while (1) {
        // 如果两轮都没有等到结果就认为失败了。
        tm.tv_sec = time(NULL) + 6;
        if (sem_timedwait(&csem, &tm)) {
            fprintf(stderr, "consumer exit\n");
            return NULL;
        }
        fprintf(stderr, "\t\tconsume %d\n", cargo[i]);
        cargo[i] = 0;
        sem_post(&psem);
        i = (i + 1) % CARGO_SIZE;
        sleep(rand() % 2 + 1);
    }
    return NULL;
}

static void test_producer_consumer() {
    print_test_header();
    sem_init(&psem, 0, CARGO_SIZE);
    sem_init(&csem, 0, 0);
    run_threads(producer, consumer, NULL);
    sem_destroy(&psem);
    sem_destroy(&csem);
}

int main(int argc, char const* argv[]) {
    test_mutex();
    test_rwlock();
    test_producer_consumer();
    return 0;
}
